#include "Process.h"
#include "thread.h"
#include "system.h"
#include "synch.h"
#include "machine.h"
#include "addrspace.h"

Lock klock("KernelThread") ;
void startProcessKernelThread(int kernelThreadAddr) {
    klock.Acquire() ;

    currentThread -> space ->InitRegisters(); // set the initial register values
    DEBUG('1', "PROCESS: Regs for thread %s(id: %d, pid:%d) initialized....\n", currentThread -> getName(), currentThread->getThreadID(), currentThread -> getParentProcess() -> getPID());

    currentThread -> space ->RestoreState(); // load page table register
    DEBUG('1', "PROCESS: State restored for thread %s(id: %d, pid:%d) ....\n", currentThread -> getName(), currentThread->getThreadID(), currentThread -> getParentProcess() -> getPID());
    klock.Release() ;

    machine->Run();
    
    
    return;
}

void startThreadKernelThread(int functionPtr) {

    DEBUG('1', "Process: Inside kernel-thread for forking the therad\n");

    klock.Acquire();
    AddrSpace* addrSpace = currentThread -> space;
    int threadid = currentThread -> getThreadID();
    Process* p = currentThread -> getParentProcess();
   
    //init the stack pointer for the htread
    unsigned int threadStackPointer = ( (addrSpace -> stackStartVpn) * PageSize ) + ( (threadid +1) * UserStackSize) - 16;
    DEBUG('5', "Process:  Thread:%s (id:%d, pid:%d) stack pointer = 0x%x(%d)\n", currentThread -> getName(), currentThread->getThreadID(), currentThread -> getParentProcess() -> getPID(), threadStackPointer, threadStackPointer);

    //rewrite the registers.
    machine->WriteRegister(PCReg, functionPtr);
    machine->WriteRegister(NextPCReg, functionPtr + 4);
    machine->WriteRegister(StackReg, threadStackPointer);
    
    currentThread -> space ->RestoreState(); // load page table register
    DEBUG('1', "PROCESS: State restored for thread %s(id: %d, pid:%d) ....\n", currentThread -> getName(), currentThread->getThreadID(), currentThread -> getParentProcess() -> getPID());
    klock.Release() ;

    machine->Run();
   
}

Process::Process(AddrSpace* space, char* pName) : threadTable(MAX_THREDS_IN_PROCESS),
ConditionTable(Max_Condition_Num), LockTable(Max_Lock_Num) {
    addrSpace = space;
   
    processId = -1;
    nChildren = 0; // the main thread.
    numUserStacks = 1 ;
    pLock = new Lock("ProcessLock");
    processName = new char[MAXFILENAME] ;

    strcpy(processName,pName);
    
}

Process::~Process() {
    nChildren = 0;
    int i;
    for (i = 0; i < Max_Lock_Num; i++) {
        Lock * l = (Lock *) LockTable.Get(i);
        if (l != NULL) {
            LockTable.Remove(i);
            delete l;
        }
    }

    for (i = 0; i < Max_Condition_Num; i++) {
        Condition *c = (Condition*) ConditionTable.Get(i);
        if (c != NULL) {
            ConditionTable.Remove(i);
            delete c;
            c = 0;
        }
    }
   
    delete (addrSpace);
}

void Process::setAddressSpace(AddrSpace* sp) {

    addrSpace = sp;

}

AddrSpace* Process::getProcessAddressSpace() {
    return addrSpace;
}

int Process::StartNewProcess() {

    // NOTE: this method is only called form the "thread-safe" method of "ProcessManager"
    // so no need to use ME here. :D

    // create a new thread into that process.
    Thread *thread = new Thread(processName);

    //make a table entry.
    //NOTE: table entry is resuable ans so id the threadid. i.e. 1-1 mapping between threadid and threadtable entry.
    int threadid = threadTable.Put((void*) thread);
    thread -> setThreadID(threadid);

    //add thread's parent process as this process.
    thread -> setParentProcess(this);

    DEBUG('1', "Process:  Thread Forked for this process.  Thread:%s (id:%d, pid:%d) \n", thread -> getName(), thread->getThreadID(), thread -> getParentProcess() -> getPID());

    // If this is the first thread, initialize currentThread
    if (currentThread == NULL) {
        currentThread = thread;
    }

    // populate the address-space with the executable file content.
    // Assumed that it;s noff compitable
    OpenFile *executable = fileSystem->Open(processName);

    if (!executable){
        DEBUG('1', "Process: Could not open the executable file %s. Process creation failed.\n", processName);
        printf("Process: Could not open the executable file \"%s\". Process creation failed.\n", processName);
        return -1 ;
    }

    int ret = addrSpace -> LoadForVM (executable) ;

    // Verify load was successful, if not die and return immediately
    if (ret == -1) {
        DEBUG('1', "Process:  AddresSpace Load failed for Process(%s) \n", processName);

        //remove this entry from the thread table
        threadTable.Remove(threadid);

        // if no thread to run.. call haly
        if (currentThread == NULL) {
            // The first load was unsuccessful, shutdown nachos.
            DEBUG('1', "Process:  No threads to run.....\n", processName);
            //interrupt->Halt();
        } else {
            //other threads may be running, just delete this thread.
            delete thread;
        }

        return -1;
    }
    DEBUG('1', "Process:  Addrspace Load successfull.  Thread:%s (id:%d, pid:%d) \n", thread -> getName(), thread->getThreadID(), thread -> getParentProcess() -> getPID());
    // Addrspace load was successful. Now run a thread.

    // add no of children.
    nChildren++;

    // Assign the created AddrSpace to the thread.
    thread -> space = addrSpace;
    
    //DEBUG('1', "Before Fork.  Thread:%s (id:%d, pid:%d) \n", thread -> getName(), thread->getThreadID(), thread -> getParentProcess() -> getPID());
    thread -> Fork((VoidFunctionPtr) startProcessKernelThread, (int) 0);

    return 1;
}

int Process::addNewThread(unsigned int funcPtr) {

    if (nChildren >= MAX_THREDS_IN_PROCESS) {
        // max no of therads are running in a process. cannor fork a thread.
        DEBUG('1', "Process:  Process %s (pid:%d) has MAX(%d) no of threads running. Cannot fork a thread. \n", getPName(), getPID(), nChildren);
        return -1;
    }

    // create a new thread into that process.
    Thread *thread = new Thread(processName);

    //make a thread table entry.
    //NOTE: table entry is resuable ans so id the threadid. i.e. 1-1 mapping between threadid and threadtable entry.
    int threadid = threadTable.Put((void*) thread);
    thread -> setThreadID(threadid);

    //add thread's parent process as this process.
    thread -> setParentProcess(this);

    //assing the address space
    thread -> space = addrSpace;
    DEBUG('1', "Process:  Thread Forked for this process.  Thread:%s (id:%d, pid:%d) \n", thread -> getName(), thread->getThreadID(), thread -> getParentProcess() -> getPID());

    // add no of children.
    nChildren++;

    //create a new stack-sapce in the addr-space. (Just increase the pagetable size)
     if ( addrSpace -> CreateNewStack(threadid) == -1 ) {
            DEBUG('6', "Process: pid:%d Couldn't create a new stack..\n", getPID());
            printf("Process: pid:%d Couldn't create a new stack..\n", getPID());
            return -1 ;
     }

    thread -> Fork((VoidFunctionPtr) startThreadKernelThread, funcPtr);

    return 1;
}

int Process::exitProcess(int code) {
    int ret = -1;

    if (nChildren == 0) {
        DEBUG('1', "Process: $$$ Error: no threads running in process %s (pid: %d)\n", processName, processId);

        return ret;
    }

    // return 0 if this is the last thread, otherwise 1 indicating there are more threads in this process.
    if (nChildren == 1) {
        ret = 0;
        DEBUG('1', "Process: Last thread in process %s (pid: %d).\n", processName, processId);
    } else {
        ret = 1;
        DEBUG('1', "Process: Some threads running in process %s (pid: %d).\n", processName, processId);
    }

    //decrement the child count.
    nChildren--;

    // destroy the stack for this thread
    addrSpace -> DestroyStack(currentThread -> getThreadID()) ;

    //remove it for the thread table
    threadTable.Remove(currentThread -> getThreadID());

    DEBUG('1', "Process: End of exit process %s (pid: %d). Returning %d ...\n", processName, processId, ret);
    return ret;
}

int Process::CreateCondition() {
    int ret;
    Condition *c = new Condition("UserCondition"); // Create New condition variabel

   // pLock ->Acquire();
    ret = ConditionTable.Put((void *) c); // Get the index position of the Condition variable created

    if (ret == -1) {
        DEBUG('3', "Process: Could not create a condition in %s (pid:%d).\n", processName, processId);
        delete c;
    } else {
        DEBUG('3', "Process: Condition %d was created successfully in %s (pid:%d).\n", ret, processName, processId);
    }
    //pLock -> Release();
    return ret;
}

int Process::DestroyCondition(int ConditionId) {
    int ret;
    //pLock -> Acquire();
    Condition *c = (Condition*) ConditionTable.Get(ConditionId); // Get the Indexed Condition.

    if (c) {
        if (c -> GetConditionStatus() == Condition::CONDITION_STATE_FREE) {
            ConditionTable.Remove(ConditionId);
            delete c;
            DEBUG('3', "Process: Condition %d destroyed in %s(pid:%d) successfully.\n.", ConditionId, processName, processId);

        } else {
            DEBUG('3', "Process: Condition %d cannot be destroyed in %s(pid:%d) as it's in use.\n.", ConditionId, processName, processId);
        }
        ret = 1;
    } else {
        DEBUG('3', "Process: Condition %d doesn't exists in %s(pid:%d)\n", ConditionId, processName, processId);
        ret = -1;
    }
    //pLock -> Release();
    return ret;
}

int Process::WaitCondition(int ConditionId, int LockId) {
    int ret;
    //pLock -> Acquire();

    Lock* l = (Lock *) LockTable.Get(LockId);
    Condition* c = (Condition *) ConditionTable.Get(ConditionId);

    if (l == NULL || c == NULL) {
        DEBUG('3', "Process: Wait cannot be serverd on Condition-%d with lock-%d in %s(pid-%d). One of these doesn't exists.\n", ConditionId, LockId, processName, processId);
        ret = -1;
    } else {
        DEBUG('3', "Process: Serving Wait on Condition-%d with lock-%d in %s(pid-%d).\n", ConditionId, LockId, processName, processId);
        c->Wait(l);
        ret = 1;
    }
   // pLock -> Release();

    return ret;
}

int Process::SignalCondition(int ConditionId, int LockId) {
    int ret;
    //pLock -> Acquire();

    Lock* l = (Lock *) LockTable.Get(LockId);
    Condition* c = (Condition *) ConditionTable.Get(ConditionId);

    if (l == NULL || c == NULL) {
        DEBUG('3', "Process: Signal cannot be serverd on Condition-%d with lock-%d in %s(pid-%d). One of these doesn't exists.\n", ConditionId, LockId, processName, processId);
        ret = -1;
    } else {
        DEBUG('3', "Process: Serving Signal on Condition-%d with lock-%d in %s(pid-%d).\n", ConditionId, LockId, processName, processId);
        c->Signal(l);
        ret = 1;
    }
   // pLock -> Release();

    return ret;
}

int Process::BroadcastCondition(int ConditionId, int LockId) {
    int ret;
   // pLock -> Acquire();

    Lock* l = (Lock *) LockTable.Get(LockId);
    Condition* c = (Condition *) ConditionTable.Get(ConditionId);

    if (l == NULL || c == NULL) {
        DEBUG('3', "Process: Broadcast cannot be serverd on Condition-%d with lock-%d in %s(pid-%d). One of these doesn't exists.\n", ConditionId, LockId, processName, processId);
        ret = -1;
    } else {
        DEBUG('3', "Process: Serving Broadcast on Condition-%d with lock-%d in %s(pid-%d).\n", ConditionId, LockId, processName, processId);
        c->Broadcast(l);
        ret = 1;
    }
    //pLock -> Release();

    return ret;
}

int Process::CreateLock() {
    int ret;

    //pLock -> Acquire();
    Lock* l = new Lock("UserLock");
    ret = LockTable.Put((void *) l);
    //there is no more space in the lock table
    if (ret == -1) {
        DEBUG('3', "Process: Cannot crate lock in %s(pid:%d). Having max no of lock already.\n", processName, processId);
        delete l;
    } else {
        DEBUG('3', "Process: Lock %d crated in %s(pid:%d).\n", ret, processName, processId);
    }
   // pLock -> Release();
    return ret;
}

int Process::DestoryLock(int LockID) {
    Lock *l = (Lock *) LockTable.Get(LockID);
    int ret;
   // pLock -> Acquire();
    //if the lock did not created before;
    if (l == NULL) {
        DEBUG('3', "Process: Lock:%d doesn't exists in %s(pid:%d). Cannot destroy.\n",LockID, processName, processId);
        ret = -1;
    }//if there are some theads still using or waiting the lock
    else if (l->getLockState() == Lock::LOCK_STATE_BUSY) {
        DEBUG('3', "Process: Lock:%d cannot be destroyed in %s(pid:%d) as it's busy.\n", LockID, processName, processId);
        ret = 1;
    } else {
        l = (Lock *) LockTable.Remove(LockID);
        //Remove successed, delete the lock
        delete l;
        DEBUG('3', "Process: Lock:%d was destroyed in %s(pid:%d).\n", LockID, processName, processId);
        ret = 1;
    }
    //pLock -> Release();
    return ret;
}

int Process::LockAcquire(int LockID) {
    int ret;

    // pLock -> Acquire();
    Lock * l = (Lock *) LockTable.Get(LockID);
    //if the lock did not created before or already destoried;
    if (l == NULL) {
        DEBUG('3', "Process: Lock:%d doesn't exists in %s(pid:%d). Cannot acquire it.\n", LockID, processName, processId);
        ret = -1;
    } else {
        DEBUG('3', "Process: Acquiring Lock:%d in %s(pid:%d).\n", LockID, processName, processId);
        ret = 1;
        l->Acquire();
    }
    //acquire operation finished,can be either success or not
    // pLock -> Release() ;

    return ret;
}

int Process::LockRelease(int LockID) {
    int ret;

    // pLock -> Acquire();
    Lock * l = (Lock *) LockTable.Get(LockID);
    //if the lock did not created before or already destoried;
    if (l == NULL) {
        DEBUG('3', "Process: Lock:%d doesn't exists in %s(pid:%d).Cannot Release lock.\n", LockID, processName, processId);
        ret = -1;
    } else {
        DEBUG('3', "Process: Releasing Lock:%d in %s(pid:%d).\n", LockID, processName, processId);
        ret = 1;
        l->Release();
    }
    //acquire operation finished,can be either success or not
    // pLock -> Release() ;

    return ret;
}
